/*
 * Copyright (C) 2015 Miquel Sas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package traderlib.core.data.entity;

import java.util.ArrayList;
import java.util.Objects;

/**
 * A definition of a relation between entities.
 *
 * @author Miquel Sas
 */
public class Relation extends ArrayList<Relation.Segment> {

	/**
	 * A relation type describes the type of a relation. There are only four possible types of relations, listed below.
	 * <ol>
	 * <li>
	 * <code>LookUp</code>: in a lookup relation, the list of local fields from the relation segments does not match the
	 * local entity key fields, and the foreign fields from the segments exactly match the foreign entity key fields. It
	 * is a many-to-one relation. A lookup relation can not be reversed because a relation must have convenient keys for
	 * the foreign entity.
	 * </li>
	 * <li>
	 * <code>Unique</code>: in an unique relation, the list of fields from the segments exactly match both the local and
	 * foreign key fields. It is a one-to-one relation.
	 * </li>
	 * <li>
	 * <code>LocalDetail</code>: it is a header-detail relation where the local entity is the detail entity. The fields
	 * from the segments match partly the local key fields and exactly the foreign key fields. It is a many-to-one
	 * relation.
	 * </li>
	 * <li>
	 * <code>ForeignDetail</code>: it is a header-detail relation where the foreign entity is the detail entity. The
	 * fields from the segments match partly the foreign key fields and exactly the local key fields. It is a
	 * one-to-many relation.
	 * </li>
	 * </ol>
	 *
	 * A <code>LocalDetail</code> relation if reversed becomes a <code>ForeignDetail</code> relation and viceversa.
	 */
	public enum Type {

		Invalid,
		Lookup,
		Unique,
		LocalDetail,
		ForeignDetail;

		/**
		 * Returns he category of the relation type.
		 *
		 * @return
		 */
		public Category getCategory() {
			if (equals(Lookup)) {
				return Category.OneToOne;
			}
			if (equals(Unique)) {
				return Category.OneToOne;
			}
			if (equals(LocalDetail)) {
				return Category.ManyToOne;
			}
			if (equals(ForeignDetail)) {
				return Category.OneToMany;
			}
			return Category.Unknown;
		}
	}

	/**
	 * Relation categories are ONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE
	 */
	public enum Category {

		Unknown,
		OneToOne,
		OneToMany,
		ManyToOne;
	}

	/**
	 * A relation segment is a small structure to pack segment info.
	 */
	public static class Segment {

		/**
		 * Local field.
		 */
		Field localField = null;
		/**
		 * Foreign field.
		 */
		Field foreignField = null;

		/**
		 * Default constructor.
		 */
		public Segment() {
			super();
		}

		/**
		 * Constructor assigning the local and foreign field.
		 *
		 * @param localField The local field.
		 * @param foreignField The foreign field.
		 */
		public Segment(Field localField, Field foreignField) {
			super();
			this.localField = localField;
			this.foreignField = foreignField;
		}

		/**
		 * Get the foreign field.
		 *
		 * @return The foreign field.
		 */
		public Field getForeignField() {
			return foreignField;
		}

		/**
		 * Set the foreign field.
		 *
		 * @param foreignField The foreign field to set.
		 */
		public void setForeignField(Field foreignField) {
			this.foreignField = foreignField;
		}

		/**
		 * Get the local field.
		 *
		 * @return The local field.
		 */
		public Field getLocalField() {
			return localField;
		}

		/**
		 * Set the local field.
		 *
		 * @param localField The local field to set.
		 */
		public void setLocalField(Field localField) {
			this.localField = localField;
		}

		/**
		 * Check for equality.
		 *
		 * @param obj Theobject to check.
		 * @return A boolean
		 */
		@Override
		public boolean equals(Object obj) {
			if (!(obj instanceof Relation.Segment)) {
				return false;
			}
			Relation.Segment seg = (Relation.Segment) obj;
			return localField.equals(seg.localField) && foreignField.equals(seg.foreignField);
		}

		/**
		 * Returnns the hash code for this segment.
		 *
		 * @return The hashcode.
		 */
		@Override
		public int hashCode() {
			int hash = 0;
			hash ^= Objects.hashCode(this.localField);
			hash ^= Objects.hashCode(this.foreignField);
			return hash;
		}

		/**
		 * Returns a string representation ofte segment.
		 *
		 * @return The segment as a string.
		 */
		@Override
		public String toString() {
			StringBuilder b = new StringBuilder(64);
			b.append(getLocalField().getAlias());
			b.append(" -> ");
			b.append(getForeignField().getAlias());
			return b.toString();
		}
	}
	/**
	 * Local entity.
	 */
	Entity localEntity = null;
	/**
	 * Foreign entity.
	 */
	Entity foreignEntity = null;
	/**
	 * This relation type.
	 */
	private Type type = null;

	/**
	 * Default constructor.
	 */
	public Relation() {
		super();
	}

	/**
	 * Add a segment to this relation.
	 *
	 * @param local The local field.
	 * @param foreign The foreign field.
	 */
	public void add(Field local, Field foreign) {
		type = null;
		add(new Segment(local, foreign));
	}

	/**
	 * Get the foreign entity.
	 *
	 * @return The foreign entity.
	 */
	public Entity getForeignEntity() {
		return foreignEntity;
	}

	/**
	 * Set the foreign entity.
	 *
	 * @param foreignEntity The foreign entity to set.
	 */
	public void setForeignEntity(Entity foreignEntity) {
		type = null;
		this.foreignEntity = foreignEntity;
	}

	/**
	 * Get the local entity.
	 *
	 * @return The local entity.
	 */
	public Entity getLocalEntity() {
		return localEntity;
	}

	/**
	 * Set the local entity.
	 *
	 * @param localEntity The local entity to set.
	 */
	public void setLocalEntity(Entity localEntity) {
		type = null;
		this.localEntity = localEntity;
	}
}
